﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.ComponentModel;

using SharedGenomics.Core;

namespace SharedGenomics.Workbench.Core
{
    public class BackgroundExecutor : IBackgroundExecutor
    {
        private BackgroundWorker _bgWorker;

        public BackgroundExecutor()
        {
            this._bgWorker = new BackgroundWorker();
            this._bgWorker.DoWork += new DoWorkEventHandler(Worker_DoWork);
            this._bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.Worker_RunWorkerCompleted);
        }

        private void Worker_DoWork(object sender, DoWorkEventArgs e)
        {
            ExecuteState state = (ExecuteState)e.Argument;
            Delegate command = state.Operation;
            BackgroundOperationResult opResult = (BackgroundOperationResult)command.DynamicInvoke();
            e.Result = new ExecuteResult(opResult, state.Callback);
        }

        private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            ExecuteResult result = (ExecuteResult)e.Result;
            BackgroundOperationResult opResult = result.Result;

            if (result.Callback != null)
            {
                //pass operation result to callback
                result.Callback.DynamicInvoke(opResult);
            }
            else if (opResult.Error != null)
            {
                //error occured but there is no callback so throw the exception
                throw opResult.Error;
            }
        }

        public void Execute(ICommand command)
        {
            this.Execute(command, null);
        }

        public void Execute(ICommand command, Action<BackgroundOperationResult> callback)
        {
            if (command == null)
                throw new ArgumentNullException("command");

            this.RunAction(command.Execute, callback);
        }

        public void Execute(Action command)
        {
            if (command == null)
                throw new ArgumentNullException("command");

            this.RunAction(command, null);
        }

        public void Execute(Action command, Action<BackgroundOperationResult> callback)
        {
            if (command == null)
                throw new ArgumentNullException("command");

            this.RunAction(command, callback);
        }

        public void Execute<T>(Func<T> command, Action<BackgroundOperationResult<T>> callback)
        {
            Func<BackgroundOperationResult<T>> wrapper = () => this.FuncWrapper(command);
            ExecuteState state = new ExecuteState(wrapper, callback);
            this._bgWorker.RunWorkerAsync(state);
        }

        private void RunAction(Action command, Delegate callback)
        {
            Debug.Assert(command != null);

            Func<BackgroundOperationResult> wrapper = () => this.ActionWrapper(command);
            ExecuteState state = new ExecuteState(wrapper, callback);
            this._bgWorker.RunWorkerAsync(state);
        }

        private BackgroundOperationResult ActionWrapper(Action act)
        {
            Debug.Assert(act != null);

            try
            {
                act();
                return new BackgroundOperationResult();
            }
            catch (Exception ex)
            {
                return new BackgroundOperationResult(ex);
            }
        }

        private BackgroundOperationResult<T> FuncWrapper<T>(Func<T> f)
        {
            try
            {
                T result = f();
                return new BackgroundOperationResult<T>(result);
            }
            catch (Exception ex)
            {
                return new BackgroundOperationResult<T>(ex);
            }
        }

        #region ExecuteResult

        private class ExecuteResult
        {
            public ExecuteResult(BackgroundOperationResult result, Delegate callback)
            {
                this.Result = result;
                this.Callback = callback;
            }

            public BackgroundOperationResult Result { get; private set; }
            public Delegate Callback { get; private set; }
        }

        #endregion

        #region ExecuteState

        private class ExecuteState
        {
            public ExecuteState(Delegate operation, Delegate callback)
            {
                this.Operation = operation;
                this.Callback = callback;
            }

            public Delegate Operation { get; private set; }
            public Delegate Callback { get; private set; }
        }

        #endregion
    }
}
